package objetos;

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * ---MONITOR---
 * Clase puente, se encarga de gestionar la entrada y la salida de los coches sobre el propio puente, 
 * ademas realiza un cambio de sentido cada "x" tiempo, ya que lo que se pretende en esta simulación, 
 * es acercarla lo mas posible a un caso real. 
 * 
 * @author Carlos Verdejo - Saul Carranza
 *
 */
public class Puente {
	private String nombre; //Cadena de texto que almacena el nombre del puente
	private int maxVehiculos; //Entero que almacena el numero máximo de vehiculos que puede haber sobre el puente
	private Queue <Vehiculo> colaInterna; //Cola que almacena los vehiculos que se encuentran sobre el puente
	private int sentIzquierda; //Entero que almacena el numero de coches que estan a la espera de entrar por el lado izquierdo del puente
	private int sentDerecha; //Entero que almacena el numero de coches que estan a la espera de entrar por el lado derecho del puente
	private Sentido sentido; //Tipo Enum que índica el sentido que tiene el puente, el mismo se altera con la función que cambioSentido()
	private Boolean activo; //Índica verdadero o falso en función de si el puente está activo o necesita repararse
	private Boolean bloqueo; //Índica verdadero o falso en función de si el puente se encuentra bloqueado
	
	//Declaracion de las condiciones del monitor
	final Lock lock = new ReentrantLock(); 
	final Condition vacio = lock.newCondition(); //Condicion para controlar si esta vacio un puente
	final Condition izquierda = lock.newCondition(); //Condicion para controlar los hilos por la izquierda
	final Condition derecha = lock.newCondition();	//Condicion para controlar los hilos por la derecha
	final Condition cambioSentido = lock.newCondition(); //Condicion para controlar el cambio de sentido del puente
	//Final declaracion de las condiciones del monitor
	
	/**
	 * Constructor parametrizado del puente
	 * @param nom Cadena de texto con el nombre del puente
	 * @param max Entero con el número máximo de vehiculos permitidos sobre el puente
	 */
	public Puente(String nom, int max){
		this.nombre = nom;
		this.maxVehiculos = max; 
		this.colaInterna = new LinkedList<Vehiculo>();
		this.sentido = Sentido.Derecha; //Por defecto cuando un puente se crea, tiene el sentido a la Derecha
		this.activo = true;
		this.bloqueo = false; //Se utiliza en caso de que quiera entrar vehiculos, pero se desea cambiar el sentido
		this.sentIzquierda=0;
		this.sentDerecha=0;
	}
	
	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}

	public int getMaxVehiculos() {
		return maxVehiculos;
	}

	public void setMaxVehiculos(int maxVehiculos) {
		this.maxVehiculos = maxVehiculos;
	}

	public Sentido getSentido() {
		return sentido;
	}

	public void setSentido(Sentido sentido) {
		this.sentido = sentido;
	}

	public Boolean getActivo() {
		return activo;
	}

	public void setActivo(Boolean activo) {
		this.activo = activo;
	}
	
	public int verColaInterna(){
		return colaInterna.size();
	}
	
	public String toString(){
		return "";
	}
	
	public int getSentIzquierda() {
		return sentIzquierda;
	}

	public int getSentDerecha() {
		return sentDerecha;
	}

	/**
	 * Metodo encargado de dar entrada a un vehiculo sobre el puente, realizando las comprobaciones pertinentes,
	 * previas a la entrada. Puede gestionar el hecho de que se encuentren coches esperando a cada lado del puente. 
	 * @param v Vehiculo que solicita la entrada en el puente
	 * @param idHilo Hilo que pretende solicitar la entrada del vehiculo en el puente
	 */
	public void entraCoche(Vehiculo v,int idHilo){
		try{
			lock.lock(); //Bloquea el monitor 
			
			//Si el puente esta activo, el sentido del puente es contrario al del vehiculo, o si la cola interna 
			//esta llena: entra en el bucle hasta que se incumpla alguna de las condiciones
			while(bloqueo || (activo && (sentido!=v.getSentido() || colaInterna.size()>=maxVehiculos))){
				
				//Dependiendo del sentido del coche, se aguarda en la condicion del sentido
				if(v.getSentido()==Sentido.Derecha){
					//System.out.println("Hilo "+idHilo+" bloqueado sentido opuesto derecha o lleno");
					sentDerecha++; //Incrementa el numero de coches esperando a la derecha del puente
					derecha.await(); //Espera en la variable de condición
					sentDerecha--; //Decrementa el numero de coches esperando a la derecha del puente
				}else{
					//System.out.println("Hilo "+idHilo+" bloqueado sentido opuesto izquierda o lleno");
					sentIzquierda++;
					izquierda.await(); //Espera en la variable de condición de vehiculos que pretenden entrar por la izquierda
					sentIzquierda--;
				}
			}
			
			//Se añande a la cola interna y da señal a vacio para que pueda sacar vehiculos
			colaInterna.add(v);
			vacio.signal(); //Da señal a la variable de condición que gestiona si el puente se encuentra vacio
		} catch (InterruptedException e) {
		}finally{
			lock.unlock(); //Desbloquea el monitor, sale de la sección crítica
		}
	}
	
	/**
	 * Metodo encargado de dar salida a un vehiculo que se encuentra sobre el puente.
	 * @return Devuelve el vehiculo al que da salida.
	 */
	public Vehiculo saleCoche(){
		Vehiculo aux=null;
		try{
			lock.lock(); //Bloquea el monitor
			
			/*Si no hay vehiculos, se aguarda en la condicion de vacio, en cuanto este vacio, dará señal para poder cambiar el sentido
			para controlar que en el caso de que terminen de pasar todos los coches de un sentido, los del sentido contrario que estuvieran
			esperando no tengan que esperar a que el propio puente cambie de sentido. Es decir, si no queda ningun vehiculo por entrar 
			en el sentido actual, el puente cambia de sentido*/
			while(activo && colaInterna.size()<=0){
				cambioSentido.signal();
				vacio.await();
			}
			
			aux=colaInterna.poll(); //Extrae el vehiculo de la cola de vehiculos que se encuentran sobre el puente
			
			//Si el puente no tiene mas vehiculos sobre el mismo da una señal sobre la condición cambioSentido
			if(colaInterna.size()<=0){
				cambioSentido.signal();
			}
			
			//Dependiendo del sentido actual que tenga el puente, se da señal sobre el mismo sentido, 
			//para que continuen pasando los vehiculos
			if(sentido==Sentido.Derecha){
				derecha.signal();
			}else{
				izquierda.signal();
			}
			
		} catch (InterruptedException e) {
		}finally{
			lock.unlock(); //Desbloquea el monitor
		}
		return aux;
	}
	
	/**
	 * Metodo encargado de realizar el cambio de sentido del tráfico del puente. 
	 * @return Cadena de texto que indica el cambio de sentido
	 */
	public String cambioSentido(){
		String salida="";
		
		try{
			lock.lock();
			
			//Se aguarda, mientras haya vehiculos dentro del puente
			while(colaInterna.size()!=0){
				bloqueo=true;
				cambioSentido.await();
			}
			
			bloqueo=false; //Indica que el puente ya no se encuentra bloqueado
			
			//Dependiendo del sentido que tenga el puente, se cambia el sentido al contrario y se da la señal al sentido en el que queda el puente
			if(sentido==Sentido.Derecha){
				sentido=Sentido.Izquierda;
				izquierda.signal();
			}else{
				sentido=Sentido.Derecha;
				derecha.signal();
			}
			
			salida=nombre.toString()+" Cambia sentido a "+sentido.toString(); 
		} catch (InterruptedException e) {
		}finally{
			lock.unlock();
		}
		return salida;
	}
	
	/**
	 * Metodo encargado de cambiar el estado actual del puente, si el puente está activo al llamar a este metodo
	 * el puente se establece como fuera de servicio, si el puente está fuera de servicio se establece como activo
	 * @return
	 */
	public String cambioEstado(){
		String salida;
		//Controla que cuando se quiera cambiar el estado del puente no se encuentren vehiculos sobre el mismo.
		if(colaInterna.size()!=0){
			salida="El nuevo estado del puente es: ";
			if(activo){
				activo=false;
				salida=salida+"fuera de servicio";
			}else{
				activo=true;
				salida=salida+"en servicio";
			}
			
		}else{
			salida="El puente no se encuentra vacio";
		}
		return salida;
	}
}
